<?php

namespace App\Services\OpenWechat;

use App\Http\Requests\Admin\Customer\WechatUserIndexRequest;
use App\Models\OpenWechat\WechatUser;
use App\Models\Panx\SkinDetector;
use App\Models\Store\HmSkinRecord;
use App\Models\Store\SkinRecord;
use App\Models\Store\Store;
use App\Models\Store\StorePivotWechatUser;
use App\Models\Store\WaxSkinRecord;
use App\Services\Presenters\HmSkinData;
use App\Services\Presenters\WaxSkinData;
use App\Services\Repositories\SkinRecord\SkinRecordRepository;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

/**
 * 开放平台微信用户仓库
 *
 * Author: lyun
 * Date: 2017/11/28
 * Created by Panxsoft.
 */
class WechatUserRepository
{
    /**
     * @var WechatUser
     */
    protected $wechat_user;

    public function __construct()
    {
        $this->wechat_user = new WechatUser();
    }

    /**
     * @param array $user_info
     * @see Mock::appUserGet()
     *
     * @return WechatUser
     */
    public function createOrUpdateWechatUserInfo(array $user_info)
    {
        /** @var WechatUser $wechat_user */
        $wechat_user = $this->wechat_user->firstOrNew([
            'openid' => $user_info['openid']
        ]);

        $info_columns = [
            'authorizer_id', 'nickname', 'language', 'headimgurl',
            'city', 'province', 'country', 'remark',
            'subscribe', 'subscribe_time', 'subscribe_scene',
        ];

        // #81 未在个人中心中修改个人信息，才需要修改 sex 字段
        if (! $wechat_user->hasPersonalInfo()) {
            $info_columns [] = 'sex';
        }

        /** @var array $user_data */
        $user_data = collect($user_info)->only($info_columns)->all();

        foreach ($user_data as $key => $val) {
            $wechat_user->{$key} = $val;
        }

        $wechat_user->nickname_unicode = json_encode($user_data['nickname']);

        $wechat_user->save();

        return $wechat_user;
    }


    /**
     * @param WechatUser $wechatUser
     *
     * @return string
     */
    public function getWeChatUserLastHmSkinInfo(WechatUser $wechatUser): string
    {
        $last_skin_record = $wechatUser->latestSkinRecordHm;
        if (! $last_skin_record) {
            return '';
        }

        $info = $last_skin_record->hmSkinRecords()
            ->orderBy('position', 'asc')
            ->get()
            // map to info string & join to string
            ->map(function (HmSkinRecord $record) {
                $info = $this->hmPositionLocaleName($record->position) . '：';
                $info .= trans('service.moisture') . $record->water . '%' . '，';
                $info .= trans('service.oil') . $record->oil . '%' . '，';
                $info .= trans('service.flex') . $record->flex . "<br>";

                return $info;
            })
            ->implode('');

        return $info;
    }

    /**
     * @param WechatUser $wechatUser
     *
     * @return string
     */
    public function getWeChatUserLastWaxSkinInfo(WechatUser $wechatUser): string
    {
        $last_skin_record = $wechatUser->latestSkinRecordWax;
        if (! $last_skin_record) {
            return '';
        }

        /** @var Collection $wax_skin_records */
        $wax_skin_records = $last_skin_record->waxSkinRecords()
            ->whereIn('itemsid', WaxSkinRecord::ITEMSID_ALL)
            ->get();

        $info = $wax_skin_records
            // #898 sort by UI requirement
            ->sortBy(function (WaxSkinRecord $rec) {
                return WaxSkinRecord::ITEMSID_SORT[$rec->itemsid];
            })
            ->values()
            // map to info string & join to string
            ->map(function (WaxSkinRecord $record) {
                return $this->waxItemLocaleName($record->itemsid) . '：' . $record->value . '分';
            })
            ->implode('，');

        return $info;
    }


    /**
     * @param WechatUser $wechatUser
     * @param array $skin_record_ids
     *
     * @param $customer_id
     * @return array - ['position' => ['water' => 'analysis', 'oil' => 'analysis', 'flex' => 'analysis']]
     */
    public function getWeChatUserLastHmSkinResult(WechatUser $wechatUser,
                                                  array $skin_record_ids, $customer_id): array
    {
        $hm_skin_records = $wechatUser->hmSkinRecords()
            ->whereIn('skin_record_id', $skin_record_ids)
            ->orderBy('created_at', 'DESC')
            ->get();

        /** @var HmSkinRecord|null $hm_eye */
        $hm_eye = $hm_skin_records->firstWhere('position', HmSkinRecord::POSITION_EYE);
        /** @var HmSkinRecord|null $hm_cheek */
        $hm_cheek = $hm_skin_records->firstWhere('position', HmSkinRecord::POSITION_CHEEK);
        /** @var HmSkinRecord|null $hm_neck */
        $hm_neck = $hm_skin_records->firstWhere('position', HmSkinRecord::POSITION_NECK);
        /** @var HmSkinRecord|null $hm_hand */
        $hm_hand = $hm_skin_records->firstWhere('position', HmSkinRecord::POSITION_HAND);

        $result = [
            HmSkinRecord::POSITION_EYE   => $this->getHmPositionSkinRecordLabels($hm_eye, $customer_id),
            HmSkinRecord::POSITION_CHEEK => $this->getHmPositionSkinRecordLabels($hm_cheek, $customer_id),
            HmSkinRecord::POSITION_NECK  => $this->getHmPositionSkinRecordLabels($hm_neck, $customer_id),
            HmSkinRecord::POSITION_HAND  => $this->getHmPositionSkinRecordLabels($hm_hand, $customer_id),
        ];

        return $result;
    }

    /**
     * @param WechatUser $wechatUser
     * @param $skin_record_ids
     * @param $customer_id
     * @return array
     */
    public function getWeChatUserLastWaxSkinResult(WechatUser $wechatUser, $skin_record_ids, $customer_id)
    {

        /** @var Collection $wax_skin_records */
        $wax_skin_records = $wechatUser->waxSkinRecords()
            ->whereIn('skin_record_id', $skin_record_ids)
            ->whereIn('itemsid', [
                WaxSkinRecord::ITEMSID_REDBLOOD,
                WaxSkinRecord::ITEMSID_MOISTURE,
                WaxSkinRecord::ITEMSID_OIL,
                WaxSkinRecord::ITEMSID_FOLLICLE,
                WaxSkinRecord::ITEMSID_PIGMENTATION,])
            ->orderBy('created_at', 'DESC')
            ->get();

        $red_blood = $wax_skin_records->where('itemsid', WaxSkinRecord::ITEMSID_REDBLOOD)->first();
        $moisture = $wax_skin_records->where('itemsid', WaxSkinRecord::ITEMSID_MOISTURE)->first();
        $oil = $wax_skin_records->where('itemsid', WaxSkinRecord::ITEMSID_OIL)->first();
        $acne = $wax_skin_records->where('itemsid', WaxSkinRecord::ITEMSID_FOLLICLE)->first();
        $pigmentation = $wax_skin_records->where('itemsid', WaxSkinRecord::ITEMSID_PIGMENTATION)->first();

        $result = [
            WaxSkinRecord::ITEMSID_REDBLOOD     => $red_blood ? WaxSkinData::transScoreLevelNameByCustom($red_blood->itemsid, $red_blood->levelsort, $customer_id) : null,
            WaxSkinRecord::ITEMSID_MOISTURE     => $moisture ? WaxSkinData::transScoreLevelNameByCustom($moisture->itemsid, $moisture->levelsort, $customer_id) : null,
            WaxSkinRecord::ITEMSID_OIL          => $oil ? WaxSkinData::transScoreLevelNameByCustom($oil->itemsid, $oil->levelsort, $customer_id) : null,
            WaxSkinRecord::ITEMSID_FOLLICLE     => $acne ? WaxSkinData::transScoreLevelNameByCustom($acne->itemsid, $acne->levelsort, $customer_id) : null,
            WaxSkinRecord::ITEMSID_PIGMENTATION => $pigmentation ? WaxSkinData::transScoreLevelNameByCustom($pigmentation->itemsid, $pigmentation->levelsort, $customer_id) : null,
        ];

        return $result;
    }

    /**
     * 获取用户最后一次测肤的分析图数据
     *
     * @param int $skin_detector_id
     * @param WechatUser $wechatUser
     * @param array $skin_record_ids
     * @param $customer_id
     * @return array
     */
    public function getLastAnalysis(int $skin_detector_id, WechatUser $wechatUser, array $skin_record_ids, $customer_id)
    {
        if ($skin_detector_id === SkinRecord::WAX) {
            $analysis = $this->getWeChatUserLastWaxSkinResult($wechatUser, $skin_record_ids, $customer_id);
        } else {
            $analysis = $this->getWeChatUserLastHmSkinResult($wechatUser, $skin_record_ids, $customer_id);
        }
        return $analysis;
    }

    /**
     * @param array|string[2] $age_range
     *
     * @return array
     */
    public static function ageToTimeRange(array $age_range): array
    {
        // [ 1~100, 1~100 ]
        list($age_start, $age_end) = $age_range;

        return [
            // start of this year - age is end time
            now()->subYears($age_end),
            // year start - (age - 1)  is begin time
            now()->subYears($age_start - 1),
        ];
    }

    /**
     * @param int $itemsid
     *
     * @return mixed
     */
    protected function waxItemLocaleName(int $itemsid)
    {
        return WaxSkinRecord::ITEM_LOCALE_MAP[trans('app.locale_language')][$itemsid];
    }

    /**
     * @param int $pos
     *
     * @return mixed
     */
    private function hmPositionLocaleName(int $pos)
    {
        return HmSkinRecord::POSITIONS_LOCALE_MAP[trans('app.locale_language')][$pos];
    }

    /**
     * @param array $store_ids
     *
     * @return \Closure
     */
    public static function withLatestSkinRecordInStores(array $store_ids): \Closure
    {
        return function (HasOne $builder) use ($store_ids) {
            $builder->whereIn((new SkinRecord)->qualifyColumn('store_id'), $store_ids);
        };
    }

    /**
     * @param array $store_ids
     * @param array $skin_detector_ids
     *
     * @return array
     */
    private static function latestSkinRecordsInStoresWith(array $store_ids,
                                                          array $skin_detector_ids): array
    {
        $with = [
            // 前端最新的测肤时间是从这个字段中取的
            "latestSkinRecord" => function (HasOne $builder) use ($store_ids, $skin_detector_ids) {
                $builder
                    ->whereIn((new SkinRecord)->qualifyColumn('store_id'), $store_ids)
                    // 由于前端会在不同列表中显示对应用户数据，需要判断是什么类型测肤
                    ->whereIn('skin_detector_id', $skin_detector_ids)
                ;
            },
        ];

        return $with;
    }

    /**
     * @param $request
     *
     * @return array
     */
    private function getRequestSkinDetectorIds(WechatUserIndexRequest $request): array
    {
        $skin_detector_ids = SkinDetector::validatedIds();

        $filter_hm = $request->hm || $request->filter_hm;
        if ($filter_hm) {
            $skin_detector_ids = [SkinDetector::ID_HM];
        }

        $filter_wax = $request->wax || $request->filter_wax;
        if ($filter_wax) {
            $skin_detector_ids = [SkinDetector::ID_WAX];
        }

        return $skin_detector_ids;
    }

    /**
     * @deprecated - 这多查一遍数据库，字段前端用数据都可以组装出来，没必要多做处理
     * @see appendPersonalCardInfo
     *
     * @param int $wechat_user_id
     *
     * @return EloquentCollection|\Illuminate\Database\Eloquent\Model|null|static|static[]|WechatUser
     */
    public function getSkinRecordWechatUserInfo(int $wechat_user_id)
    {
        /** @var WechatUser $wechat_user_info */
        $wechat_user_info = (new WechatUser)->find(
            $wechat_user_id,
            ['id', 'headimgurl', 'name', 'sex', 'phone', 'born_at', 'nickname_unicode', 'province', 'city']
        );
        $wechat_user_info->name = $wechat_user_info->name ?: $wechat_user_info->nickname;
        $wechat_user_info->province_city = $wechat_user_info->province . $wechat_user_info->city;
        $wechat_user_info->addHidden(['born_at', 'id']);

        return $wechat_user_info;
    }

    /**
     * @param WechatUser $wechatUser
     *
     * @return EloquentCollection|\Illuminate\Database\Eloquent\Model|null|static|static[]|WechatUser
     */
    public function appendPersonalCardInfo(WechatUser $wechatUser)
    {
        $wechatUser->name = $wechatUser->name ?: $wechatUser->nickname;
        $wechatUser->province_city = $wechatUser->province . $wechatUser->city;

        return $wechatUser;
    }

    /**
     * @param array $wechat_user_store_ids
     * @param $wechat_user
     *
     * @return array|EloquentCollection|\Illuminate\Support\Collection|static[]
     */
    public function getSkinRecordsGroupByStoreIdSkinDetector(array $wechat_user_store_ids,
                                                             WechatUser $wechat_user): Collection
    {
        $records_group_by_store_and_detector = (new SkinRecord)
            ->whereIn('store_id', $wechat_user_store_ids)
            ->where('wechat_user_id', $wechat_user->id)
            ->whereIn('skin_detector_id', SkinDetector::validatedIds())
            ->select(
                'store_id AS store_id',
                'skin_detector_id AS skin_detector_id',
                DB::raw('COUNT(*) AS skin_record_count')
            )
            ->groupBy('store_id', 'skin_detector_id')
            ->get();

        $go_store_groups = $records_group_by_store_and_detector->groupBy(['store_id', 'skin_detector_id']);

        return $go_store_groups;
    }


    /**
     * @deprecated - 前端根本没有使用这个数据
     *
     * @param WechatUser $wechat_user
     * @param $store_ids
     *
     * @return EloquentCollection|static[]
     */
    public function getGoStores(WechatUser $wechat_user,$store_ids)
    {
        $wechat_user_store_ids = $wechat_user
            ->skinRecords()
            ->pluck('store_id');

        $go_stores = (new Store)
            ->whereIn('id', $wechat_user_store_ids)
            ->whereIn('id',$store_ids)
            ->select(['id', 'name', 'store_img'])
            ->limit(4)
            ->get();

        return $go_stores;
    }

    /**
     * @deprecated - 这个方法不应在此 repo 中
     * @see SkinRecordRepository::formatStartEndDateRange
     *
     * @param Carbon $date
     * @return array
     */
    public function getSkinRecordStartDate(Carbon $date): array
    {
        $skin_record_date = [
            'start' => [
                'year'  => $date->year,
                'month' => $date->month,
                'day'   => $date->day,
            ],
            'end'   => [
                'year'  => now()->year,
                'month' => now()->month,
                'day'   => now()->day,
            ],
        ];
        return $skin_record_date;
    }

    /**
     * @deprecated - v2.3.9 改版后的 UI 中前端并没有使用 last_hm_skin_info 与 last_wax_skin_info，前端都注释了
     *
     * @param WechatUser $wechatUser
     */
    public function appendLastStoresSkinRecordInfo(WechatUser $wechatUser): void
    {
        $wechatUser->last_hm_skin_info = $this->getWeChatUserLastHmSkinInfo($wechatUser);
        $wechatUser->last_wax_skin_info = $this->getWeChatUserLastWaxSkinInfo($wechatUser);
    }

    /**
     * 反查 Hm 的数据对应的微信用户
     *
     * @uses HmSkinRecordPresenter::assembleLabelGroupData()
     *
     * @param array $store_ids
     * @param WechatUserIndexRequest|\App\Http\Requests\Admin\Store\WechatUserIndexRequest|Request $request
     *
     * @return \Closure
     */
    public function reverseFilterHmLabelWxUsers(array $store_ids, Request $request)
    {
        return function (Builder $wx_user_query) use ($store_ids, $request) {
            $latest_hm_record_ids = SkinRecord::latestStoresWechatUsersTypeSkinRecords(
                $store_ids, SkinDetector::ID_HM
            )->modelKeys();

            $label = $request->hm_label;
            $label_key = $request->hm_label_key;
            $position = $request->hm_position;

            $wx_user_ids = (new HmSkinRecord)
//                ->where(HmSkinData::skinRecordWhereRangeByLabelKey($label, $label_key))
                ->where(HmSkinData::hmItemLevelSortWhereRange($label, last(explode('_', $label_key)))) //前端在使用water_label_5的字段
                ->whereIn('skin_record_id', $latest_hm_record_ids)
                ->where('position', $position)
                ->pluck('wechat_user_id')
                ->unique() // 这里数据量比较小
                ->all();

            $wx_user_query->whereKey($wx_user_ids);
        };
    }


    /**
     * 反查饼图 Wax 的数据对应的微信用户
     *
     * @param array $store_ids
     * @param WechatUserIndexRequest|\App\Http\Requests\Admin\Store\WechatUserIndexRequest|Request $request
     *
     * @return \Closure
     */
    public function reverseFilterWaxSidLevelWxUsers(array $store_ids, Request $request)
    {
        return function (Builder $wx_user_query) use ($store_ids, $request) {
            $latest_skin_record_ids = SkinRecord::latestStoresWechatUsersTypeSkinRecords(
                $store_ids, SkinDetector::ID_WAX
            )->modelKeys();

            $itemsid = $request->wax_itemsid;
            $levelsort = $request->wax_levelsort;

            $wx_user_ids = (new WaxSkinRecord)
                ->where(compact('itemsid', 'levelsort'))
                ->whereIn('skin_record_id', $latest_skin_record_ids)
                ->pluck('wechat_user_id')
                ->unique()
                ->all();

            $wx_user_query->whereKey($wx_user_ids);
        };
    }

    /**
     * @param WechatUser $wechatUser
     * @param $store_ids
     *
     * @return WechatUser
     */
    public function appendStoresSkinRecordCountInfo(WechatUser $wechatUser,
                                                    array $store_ids)
    {
        $skin_records = $wechatUser
            ->skinRecords()
            ->whereIn('skin_detector_id', SkinDetector::validatedIds())
            ->whereIn('store_id', $store_ids)
            ->get();

        $hm_skin_count = $skin_records->where('skin_detector_id',SkinRecord::HM)->count();
        $wax_skin_count = $skin_records->where('skin_detector_id',SkinRecord::WAX)->count();

        $wechatUser->all_skin_count = $hm_skin_count + $wax_skin_count;
        $wechatUser->hm_skin_count = $hm_skin_count;
        $wechatUser->wax_skin_count = $wax_skin_count;

        return $wechatUser;
    }

    /**
     * @param HmSkinRecord|null $record
     *
     * @param $customer_id
     * @return array
     */
    private function getHmPositionSkinRecordLabels(?HmSkinRecord $record,  $customer_id): array
    {
        if (! $record) {
            return [
                'water' => null,
                'oil'   => null,
                'flex'  => null,
            ];
        }

        return   [
            'water' => HmSkinData::transScoreTitleByCustom(HmSkinData::HM_WATER, $record->water, $customer_id),
            'oil'   => HmSkinData::transScoreTitleByCustom(HmSkinData::HM_OIL, $record->oil, $customer_id),
            'flex'  => HmSkinData::transScoreTitleByCustom(HmSkinData::HM_FLEX, $record->flex, $customer_id),
        ];
    }

    /**
     * 后台（商家 / 店铺）肤质详情微信用户列表通用查询
     *
     * @param WechatUserIndexRequest|\App\Http\Requests\Admin\Store\WechatUserIndexRequest|Request $request
     *
     * @param array $store_ids
     *
     * @return Builder
     */
    public function adminSkinRecordIndexQuery(WechatUserIndexRequest $request, array $store_ids): Builder
    {
        $keywords = $request->keywords;
        $time_range = $request->age_range ? WechatUserRepository::ageToTimeRange($request->age_range) : null;

        // 需要按类型查找才查询
        $is_search_by_type = $request->hm || $request->wax;
        $store_record_ids = $is_search_by_type
            ? (new SkinRecord)->whereIn('store_id', $store_ids)->pluck('id')->all()
            : [];

        $store_wx_user_ids = StorePivotWechatUser::storesWechatUsersIds($store_ids);
        $request_detector_ids = $this->getRequestSkinDetectorIds($request);

        $wechat_users_query = $this->wechat_user
            ->whereKey($store_wx_user_ids)
            ->filterSkinRecordByStores($request->has_skin_record, $store_ids)
            ->filterHmSkinRecord($request->hm, $store_record_ids)
            ->filterWaxSkinRecord($request->wax, $store_record_ids)
            ->filterTimeRange($time_range, 'born_at')
            ->filterSex($request->sex)
            ->columnsKeywordOrLike(['nickname', 'phone', 'name', 'nickname_unicode'], $keywords)

            // #724 #1056 最新测肤时间应是对应该店铺的，有几个客户同时使用魔妆镜的公众号
            ->with(WechatUserRepository::latestSkinRecordsInStoresWith($store_ids, $request_detector_ids))
            ->select([
                'wechat_users.id', 'openid', 'sex', 'phone',
                'name', 'nickname', 'nickname_unicode', 'subscribe',
                'city', 'province', 'country', 'headimgurl', 'born_at',
                DB::raw('skin_records.created_at as latest_skin_record_created_at'),
            ])
            ->leftJoinSub(
                SkinRecordRepository::latestStoreSkinRecordsGroupBy($store_ids), 'skin_records',
                'wechat_users.id', '=', 'skin_records.wechat_user_id'
            )
            ->orderBy(
                $request->order_by ?? 'latest_skin_record_created_at',
                $request->sort ?? 'desc'
            )
            // IDE 无法索引 when 后面的方法，所以 when 放到最后了
            ->when(
                $request->filter_hm,
                $this->reverseFilterHmLabelWxUsers($store_ids, $request))
            ->when(
                $request->filter_wax,
                $this->reverseFilterWaxSidLevelWxUsers($store_ids, $request)
            )
        ;

        return $wechat_users_query;
    }
}
